﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using HtmlAgilityPack;
using IDT.Entity;
using log4net;

namespace IDT.Downloader
{
    public class ImageDownloader
    {
        public string NotFound = "Not Found";
        public string WebError = "网络出错出错:{0}";
        public readonly WebInfo WebInfo;
        private ILog logger = LogManager.GetLogger(typeof(ImageDownloader));

        #region Construct

        public ImageDownloader(WebInfo webInfo)
        {
            WebInfo = webInfo;
        }

        #endregion

        #region Public Methods

        public async Task<CategoryCollection> GetCategories()
        {
            CategoryCollection categories = new CategoryCollection();

            await Task.Run(() =>
            {
                try
                {
                    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(WebInfo.StartUrl);

                    using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
                    {
                        HtmlDocument doc = GetHtmlDocument(response);

                        HtmlNodeCollection categoryAnchors = doc.DocumentNode.SelectNodes(WebInfo.NavgationLinkPath);

                        if (categoryAnchors != null && categoryAnchors.Count > 0)
                        {
                            int index = 1;
                            categories.AddRange(from categoryAnchor in categoryAnchors
                                let categoryName = categoryAnchor.InnerText
                                let categoryUrl =
                                    categoryAnchor.GetAttributeValue("href", NotFound).StartsWith("/")
                                        ? $"{WebInfo.HomeUrl}{categoryAnchor.GetAttributeValue("href", NotFound)}"
                                        : categoryAnchor.GetAttributeValue("href", NotFound)
                                where
                                    (categoryUrl.StartsWith(WebInfo.HomeUrl) &&
                                     !categoryUrl.Trim('/').Equals(WebInfo.HomeUrl) &&
                                     !WebInfo.IgnoredUrls.Exists(
                                         x => categoryUrl.StartsWith(x, StringComparison.InvariantCultureIgnoreCase)))
                                select new Category
                                {
                                    CategoryId = index++,
                                    Name = categoryName,
                                    Url = categoryUrl,
                                    IsActive = true,
                                    Albums = new List<Album>()
                                });
                        }
                        else
                        {
                            categories.Add(new Category
                            {
                                Name = WebInfo.Name,
                                Url = WebInfo.StartUrl,
                                IsActive = true,
                                Albums = new List<Album>()
                            });
                        }
                    }
                }
                catch (Exception exception)
                {
                    logger.Debug("GetCategories", exception);
                }
            });

            return categories;
        }

        private HtmlDocument GetHtmlDocument(HttpWebResponse response)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.Load(response.GetResponseStream(), Encoding.GetEncoding(WebInfo.Encoding));
            return doc;
        }

        public async Task<List<Album>> GetAlbumsForCategory(Category category)
        {
            List<Album> albums = new List<Album>();

            try
            {
                if (category != null)
                {
                    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(category.Url);
                    using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
                    {
                        HtmlDocument doc = GetHtmlDocument(response);

                        HtmlNode finalNode = doc.DocumentNode.SelectSingleNode(WebInfo.LastAlbumXpath);
                        List<string> pagerUrls = new List<string>();
                        if (finalNode != null)
                        {
                            // list_6_2.html, list_6_3.html
                            string url = finalNode.GetAttributeValue("href", NotFound);
                            string[] urlSegments = url.Split(new[] {"_", "."},
                                StringSplitOptions.RemoveEmptyEntries);
                            string urlFormat = urlSegments[0] + "_" + urlSegments[1] + "_{0}" + ".html";

                            int maxPageIndex;

                            if (int.TryParse(urlSegments[2], out maxPageIndex))
                            {
                                string parentUrl =
                                    category.Url.TrimEnd(
                                        category.Url.Split(new[] {'/'}, StringSplitOptions.None).Last().ToCharArray());
                                for (int i = maxPageIndex; i >= 2; i--)
                                {
                                    string pageUrl = string.Format(parentUrl + urlFormat, i);
                                    pagerUrls.Add(pageUrl);
                                }
                            }
                        }
                        pagerUrls.Insert(pagerUrls.Count, category.Url);

                        albums = await GetAlbums(category, pagerUrls);
                    }
                }
            }
            catch (Exception exception)
            {
                logger.Debug("GetCategories", exception);
            }

            return albums;
        }

        public async Task<List<Photo>> GetPhotosForAlbum(Album album)
        {
            List<string> photoUrls = new List<string>();
            await Task.Run(() =>
            {
                try
                {
                    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(album.Url);
                    using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
                    {
                        HtmlDocument doc = GetHtmlDocument(response);

                        HtmlNode totalPageCountNode = doc.DocumentNode.SelectSingleNode(WebInfo.TotalImageCountXpath);
                        if (totalPageCountNode != null)
                        {
                            string totalPageCoungNodeInnerText = totalPageCountNode.InnerText.Trim(" :".ToCharArray());
                            string maxPageStr = totalPageCoungNodeInnerText.Substring(1,
                                totalPageCoungNodeInnerText.Length - 2);
                            // 2063_9.html, 2063_10.html
                            string urlFormat = album.Url.Substring(0, album.Url.Length - ".html".Length) + "_{0}" +
                                               ".html";

                            int maxPageIndex;
                            if (int.TryParse(maxPageStr, out maxPageIndex))
                            {
                                for (int i = 2; i <= maxPageIndex; i++)
                                {
                                    string photoUrl = string.Format(urlFormat, i);
                                    photoUrls.Add(photoUrl);
                                }
                            }
                        }
                        photoUrls.Insert(0, album.Url);
                    }
                }
                catch (Exception exception)
                {
                    logger.Debug("GetPhotosForAlbum", exception);
                }
            });

            return await GetPhotos(album, photoUrls);
        }

        #endregion

        #region Private Methods

        private async Task<List<Album>> GetAlbums(Category category, IEnumerable<string> pagerUrls)
        {
            List<Album> albums = category.Albums;

            await Task.Run(() =>
            {
                List<string> urls = pagerUrls as List<string> ?? pagerUrls.ToList();
                if (pagerUrls != null && urls.Any())
                {
                    int albumIndex = 1;
                    foreach (string pagerUrl in urls)
                    {
                        try
                        {
                            HttpWebRequest request = (HttpWebRequest) WebRequest.Create(pagerUrl);

                            using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
                            {
                                HtmlDocument doc = GetHtmlDocument(response);

                                HtmlNodeCollection albumImageNodes = doc.DocumentNode.SelectNodes(WebInfo.AlbumUrlXpath);

                                List<HtmlNode> albumNodes = albumImageNodes.ToList();
                                albumNodes.Reverse();
                                if (albumNodes != null && albumNodes.Any())
                                {

                                    foreach (HtmlNode albumNode in albumNodes)
                                    {
                                        HtmlNode imgCoverNode = albumNode.SelectSingleNode("./img");
                                        string imgCoverSrc =
                                            !imgCoverNode.GetAttributeValue("src", NotFound).Equals(NotFound)
                                                ? imgCoverNode.GetAttributeValue("src", NotFound)
                                                : imgCoverNode.GetAttributeValue("lazysrc", NotFound);
                                        string albumName =
                                            !albumNode.GetAttributeValue("alt", NotFound).Equals(NotFound)
                                                ? albumNode.GetAttributeValue("alt", NotFound)
                                                : albumNode.GetAttributeValue("title", NotFound);
                                        albumName = !albumName.Equals(NotFound)
                                            ? albumName
                                            : !imgCoverNode.GetAttributeValue("alt", NotFound).Equals(NotFound)
                                                ? imgCoverNode.GetAttributeValue("alt", NotFound)
                                                : imgCoverNode.GetAttributeValue("title", NotFound);
                                        string albumUrl = albumNode.GetAttributeValue("href", NotFound);

                                        Album album = new Album
                                        {
                                            CategoryId = category.CategoryId,
                                            AlbumId = albumIndex++,
                                            Category = category,
                                            Name = albumName.Trim(),
                                            Url = albumUrl.StartsWith("/") ? $"{WebInfo.HomeUrl}{albumUrl}" : albumUrl
                                        };

                                        Photo albumCover = new Photo
                                        {
                                            Album = album,
                                            Name =
                                                "index." +
                                                imgCoverSrc.Split(new[] {"/", "."},
                                                    StringSplitOptions.RemoveEmptyEntries)
                                                    .Last(),
                                            Url = imgCoverSrc.StartsWith("/")
                                                ? $"{WebInfo.HomeUrl}{imgCoverSrc}"
                                                : imgCoverSrc
                                        };

                                        album.AlbumCover = albumCover;
                                        albums.Add(album);
                                    }
                                }
                            }
                        }
                        catch (Exception exception)
                        {
                            logger.Debug("GetAlbums", exception);
                        }
                    }
                }
            });

            return albums;
        }

        private async Task<List<Photo>> GetPhotos(Album album, IEnumerable<string> photoPageUrls)
        {
            List<Photo> photos = album.Photos;
            return await Task.Run(() =>
            {
                IEnumerable<string> urls = photoPageUrls as string[] ?? photoPageUrls.ToArray();
                IEnumerable<string> pageUrls = photoPageUrls as IList<string> ?? urls.ToList();
                if (photoPageUrls != null && pageUrls.Any())
                {
                    int index = 0;
                    foreach (string photoPageUrl in pageUrls)
                    {
                        try
                        {
                            HttpWebRequest request = (HttpWebRequest) WebRequest.Create(photoPageUrl);
                            using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
                            {
                                HtmlDocument doc = GetHtmlDocument(response);

                                HtmlNodeCollection imageNodeCollection = doc.DocumentNode.SelectNodes(WebInfo.ImageXpath);
                                HtmlNode insertedTimeNode =
                                    doc.DocumentNode.SelectSingleNode(WebInfo.ImageInsertedTimeXpath);

                                // Gets the inserted time.
                                DateTime insertedTime;
                                Regex regex = new Regex(@"\d{4}-\d{1,2}-\d{1,2}\s*(\d{2}:\d{2}:\d{2})?");
                                Match match = regex.Match(insertedTimeNode.InnerText);

                                if (match.Success)
                                {
                                    DateTime.TryParse(match.Value.Trim(), out insertedTime);
                                }
                                else
                                {
                                    insertedTime = DateTime.Now;
                                }


                                foreach (HtmlNode imageNode in imageNodeCollection)
                                {
                                    index++ ;
                                    string srcAttributeValue = imageNode.GetAttributeValue("src", NotFound);
                                    string photoName =
                                        $"{album.Name}({index:D2}).{GetImageExtension(srcAttributeValue)}";

                                    album.InsertedTime = insertedTime;
                                    album.UpdatedTime = insertedTime;
                                    Photo photo = new Photo
                                    {
                                        AlbumId = album.AlbumId,
                                        PhotoId = index,
                                        Album = album,
                                        Name = photoName,
                                        Url =
                                            srcAttributeValue.StartsWith("/")
                                                ? $"{WebInfo.HomeUrl}{srcAttributeValue}"
                                                : srcAttributeValue,
                                    };

                                    photos.Add(photo);
                                }
                            }
                        }
                        catch (Exception exception)
                        {
                            logger.Debug($"GetPhotos-{photoPageUrl}", exception);
                        }
                    }
                }

                return photos;
            });
        }

        private string GetImageExtension(string imageUrl)
        {
            // Default extension
            string extension = "jpg";

            if (!string.IsNullOrEmpty(imageUrl))
            {
                extension = imageUrl.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries).Last();
            }

            return extension;
        }

        #endregion
    }
}